home *** CD-ROM | disk | FTP | other *** search
/ CU Amiga Super CD-ROM 23 / CU Amiga - Super CD-ROM 23 (June 1998).iso / CreatingGames / GameCreators / Inform / how-to-syntax-colour-inform.tx < prev    next >
Encoding:
Text File  |  1997-03-15  |  20.6 KB  |  545 lines

  1. Subject: How to syntax-colour Inform
  2. Date: Wed, 17 Dec 1997 18:46:34 +0000 (GMT)
  3. From: Graham Nelson <graham@gnelson.demon.co.uk>
  4. Newsgroups: rec.arts.int-fiction
  5.  
  6. [This is going to be a new section in the Inform Technical Manual,
  7. which seems as good a place to keep it as any, but in the mean time
  8. it's been requested several times on the newsgroup, hence this
  9. posting.  Comments welcome -- GN.]
  10.  
  11. How to syntax-colour Inform source code
  12. ---------------------------------------
  13.  
  14. "Syntax colouring" is an automatic process which some text editors apply
  15. to the text being edited: the characters are displayed just as they are,
  16. but with artificial colours added according to what the text editor thinks
  17. they mean.  The editor is in the position of someone going through a book
  18. colouring all the verbs in red and all the nouns in green: it can only do
  19. so if it understands how to tell a verb or a noun from other words.
  20. Many good text editors have been programmed to syntax colour for languages
  21. such as C, and a few will allow users to reprogram them to other languages.
  22.  
  23. One such is the popular Acorn RISC OS text editor "Zap", for which the
  24. author has written an extension mode called "ZapInform".  ZapInform
  25. contributes colouring rules for the Inform language and as over a dozen
  26. people have now asked me how it works, while the original is written
  27. in ARM assembly (a language rather less widely spoken than Middle Egyptian)
  28. it seems worth documenting the main algorithm.
  29.  
  30. (ZapInform does a number of other useful things, including pasting in
  31. template objects and rooms when commanded from a mouse-accessed menu:
  32. for instance, you can create a simple game with two or three mouse
  33. clicks and a few object names typed in to a dialogue box, then click
  34. to save and compile the result.  See the ZapInform manual for details.)
  35.  
  36. (a)  State values
  37.  
  38. ZapInform associates a 32-bit number called the "state" with every
  39. character position.
  40.  
  41. The "state" is as follows.  11 of the upper 16 bits hold flags, the
  42. rest being unused:
  43.  
  44.    32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17
  45.                                                 comment
  46.                                              single-quoted text
  47.                                           double-quoted text
  48.                                        statement
  49.                                     after marker
  50.                                  highlight flag
  51.                               highlight all flag
  52.                            colour backtrack
  53.                         after-restart-flag
  54.                      wait-direct (waiting for a directive)
  55.                   dont-know-flag
  56.  
  57. These flags make up the "outer state" while the lower 16 bits holds
  58. a number pompously called the "inner state":
  59.  
  60.              0    after WS (WS = white space or start of line or comma)
  61.              1    after WS then "-"
  62.              2    after WS then "-" and ">" [terminal]
  63.              3    after WS then "*" [terminal]
  64.  
  65.           0xFF    after junk
  66.    0x100*N + S    after WS then an Inform identifier N+1 characters long
  67.                   itself in state S:
  68.                   101 w    202 wi   303 wit   404 with
  69.                   111 h    212 ha   313 has
  70.                   121 c    222 cl   323 cla   424 clas   525 class
  71.  same + 0x8000    when complete [terminal]
  72.  
  73. In practice it would be madness to try to actually store the state
  74. of every character position in memory (it would occupy four times as
  75. much space as the file itself).  Instead, ZapInform caches just one
  76. state value, the one most recently calculated, and uses a process
  77. called "scanning" to determine new states.  That is, given that we
  78. know the state at character X and want to know the state at character
  79. Y, we can find out by scanning each character between X and Y,
  80. altering the state according to each one.
  81.  
  82. It might possible save some time to cache more state values than
  83. this (say, the state values at the start of every screen-visible
  84. line of text, or some such) but the complexity of doing this doesn't
  85. seem worthwhile on my implementation.  Scanning is a quick process
  86. because the Zap text editor stores the entire file in almost contiguous
  87. memory, easy to run through, and the state value can be kept in a
  88. single CPU register while this is done.
  89.  
  90. (b)  Scanning text
  91.  
  92. Let us number the characters in a file 1, 2, 3, ...
  93.  
  94. The state before character 1 is always 0x02000000: that is, inner
  95. state zero and outer state with only the waiting-for-directive flag set.
  96. (One can think of this as the state of an imaginary "character 0".)
  97. The state at character N+1 is then a function of the state at
  98. character N and what character is actually there.  Thus,
  99.  
  100.        State(0) = 0x02000000
  101.  
  102. and for all N >= 0,
  103.  
  104.        State(N+1) = Scanning_function(State(N), Character(N+1))
  105.  
  106. And here is what the scanning function does:
  107.  
  108.     1.  Is the comment bit set?
  109.            Is the character a new-line?
  110.               If so, clear the comment bit.
  111.               Stop.
  112.  
  113.     2.  Is the double-quote bit set?
  114.            Is the character a double-quote?
  115.               If so, clear the double-quote bit.
  116.               Stop.
  117.  
  118.     3.  Is the single-quote bit set?
  119.            Is the character a single-quote?
  120.               If so, clear the single-quote bit.
  121.               Stop.
  122.  
  123.     4.  Is the character a single quote?
  124.            If so, set the single-quote bit and stop.
  125.  
  126.     5.  Is the character a double quote?
  127.            If so, set the double-quote bit and stop.
  128.  
  129.     6.  Is the character an exclamation mark?
  130.            If so, set the comment bit and stop.
  131.  
  132.     7.  Is the statement bit set?
  133.            If so:
  134.               Is the character "]"?
  135.                  If so:
  136.                     Clear the statement bit.
  137.                     Stop.
  138.  
  139.               If the after-restart bit is clear, stop.
  140.  
  141.               Run the inner finite state machine.
  142.  
  143.               If it results in a keyword terminal (that is, a terminal
  144.               which has inner state 0x100 or above):
  145.                  Set colour-backtrack (and record the backtrack colour
  146.                  as "function" colour).
  147.                  Clear after-restart.
  148.  
  149.               Stop.
  150.  
  151.            If not:
  152.               Is the character "["?
  153.                  If so:
  154.                     Set the statement bit.
  155.                     If the after-marker bit is set, set after-restart.
  156.                     Stop.
  157.  
  158.               Run the inner finite state machine.
  159.  
  160.               If it results in a terminal:
  161.                  Is the inner state 2 [after "->"] or 3 [after "*"]?
  162.                     If so:
  163.                        Set after-marker.
  164.                        Set colour-backtrack (and record the backtrack
  165.                        colour as "directive" colour).
  166.                        Zero the inner state.
  167.                  [If not, the terminal must be from a keyword.]
  168.                  Is the inner state 0x404 [after "with"]?
  169.                     If so:
  170.                        Set colour-backtrack (and record the backtrack
  171.                        colour as "directive" colour).
  172.                        Set after-marker.
  173.                        Set highlight.
  174.                        Clear highlight-all.
  175.                  Is the inner state 0x313 ["has"] or 0x525 ["class"]?
  176.                     If so:
  177.                        Set colour-backtrack (and record the backtrack
  178.                        colour as "directive" colour).
  179.                        Set after-marker.
  180.                        Clear highlight.
  181.                        Set highlight-all.
  182.                  If the inner state isn't one of these: [so that recent
  183.                  text has formed some alphanumeric token which might or
  184.                  might not be a reserved word of some kind]
  185.                     If waiting for directive is set:
  186.                           Set colour-backtrack (and record the backtrack
  187.                           colour as "directive" colour)
  188.                     If not, but highlight-all is set:
  189.                           Set colour-backtrack (and record the backtrack
  190.                           colour as "property" colour)
  191.                     If not, but highlight is set:
  192.                           Clear highlight.
  193.                           Set colour-backtrack (and record the backtrack
  194.                           colour as "property" colour).
  195.  
  196.                  Is the character ";"?
  197.                     If so:
  198.                        Set wait-direct.
  199.                        Clear after-marker.
  200.                        Clear after-restart.
  201.                        Clear highlight.
  202.                        Clear highlight-all.
  203.                  Is the character ","?
  204.                     If so:
  205.                        Set after-marker.
  206.                        Set highlight.
  207.  
  208.               Stop.
  209.  
  210. The "inner finite state machine" adjusts only the inner state, and
  211. always preserves the outer state.  It not only changes an old inner
  212. state to a new inner state, but sometimes returns a "terminal" flag
  213. to signal that something interesting has been found.
  214.  
  215.           State      Condition      Go to state     Return terminal-flag?
  216.           0          if "-"         1
  217.                      if "*"         3               yes
  218.                      if space, "#",
  219.                         newline     0
  220.                      if "_"         0x100
  221.                      if "w"         0x101
  222.                      if "h"         0x111
  223.                      if "c"         0x121
  224.                      other letters  0x100
  225.                      otherwise      0xFF
  226.           1          if ">"         2               yes
  227.                      otherwise      0xFF
  228.           2          always         0
  229.           3          always         0
  230.           0xFF       if space,
  231.                         newline     0
  232.                      otherwise      0xFF
  233.  
  234.           all 0x100+ states:
  235.                      if not alphanumeric, add
  236.                         0x8000 to the state         yes
  237.           then for the following states:
  238.           0x101      if "i"         0x202
  239.                      otherwise      0x200
  240.           0x202      if "t"         0x303
  241.                      otherwise      0x300
  242.           0x303      if "h"         0x404
  243.                      otherwise      0x400
  244.           0x111      if "a"         0x212
  245.                      otherwise      0x200
  246.           0x212      if "s"         0x313
  247.                      otherwise      0x300
  248.           0x121      if "l"         0x222
  249.                      otherwise      0x200
  250.           0x222      if "a"         0x323
  251.                      otherwise      0x300
  252.           0x323      if "s"         0x424
  253.                      otherwise      0x400
  254.           0x424      if "s"         0x525
  255.                      otherwise      0x500
  256.           but for all other 0x100+ states:
  257.                      if alphanumeric, add
  258.                         0x100 to the state
  259.  
  260.           0x8000+    always         0
  261.  
  262. (Note that if your text editor stores tabs as characters in their own
  263. right (usually 0x09) rather than rows of spaces, tab should be included
  264. with space and newline in the above.)
  265.  
  266. Briefly, the finite state machine can be left running until it returns
  267. a terminal, which means it has found "->", "*" or a completed Inform
  268. identifier: and it detects "with", "has" and "class" as special keywords
  269. amongst these identifiers.
  270.  
  271. (c)  Initial colouring
  272.  
  273. ZapInform colours one line of visible text at a time.  For instance, it
  274. might be faced with this:
  275.  
  276.      Object -> bottle "~Heinz~ bottle"
  277.  
  278. And it outputs an array of colours for each character position in the
  279. line, which the text editor can then use in actually displaying the text.
  280.  
  281. It works out the state before the first character of the line (the "O"),
  282. then scans through the line.  For each character, it determines the
  283. initial colour as a function of the state at that character:
  284.  
  285.   If single-quote or double-quote is set, then quoted text colour.
  286.   If comment is set, then comment colour.
  287.   If statement is set:
  288.      Use code colour
  289.         unless the character is "[" or "]", in which case use
  290.            function colour,
  291.         or is a single or double quote, in which case use quoted text
  292.            colour.
  293.   If not:
  294.      Use foreground colour
  295.         unless the character is "," or ";" or "*" or ">", in which
  296.            case use directive colour,
  297.         or the character is "[" or "]", in which case use
  298.            function colour,
  299.         or is a single or double quote, in which case use quoted text
  300.            colour.
  301.  
  302. However, the scanning algorithm sometimes signals that a block of
  303. text must be "backtracked" through and recoloured.  For instance,
  304. this happens if the white space after the sequence "c", "l", "a",
  305. "s" and "s" is detected when in a context where the keyword "class"
  306. is legal.  The scanning algorithm does this by setting the "colour
  307. backtrack" bit in the outer state.  Note that the number of characters
  308. we need to recolour backwards from the current position has been
  309. recorded in bits 9 to 16 of the inner state (which has been counting
  310. up lengths of identifiers), while the scanning algorithm has also
  311. recorded the colour to be used.  For instance, in
  312.  
  313.      Object -> bottle "~Heinz~ bottle"
  314.            ^  ^      ^
  315.  
  316. backtracks of size 6, 2 and 6 are called for at the three marked
  317. spaces.  Note that a backtrack never crosses a new-line.
  318.  
  319. ZapInform uses the following chart of colours:
  320.  
  321.     name                   default actual colour
  322.  
  323.     foreground             navy blue
  324.     quoted text            grey
  325.     comment                light green
  326.     directive              black
  327.     property               red
  328.     function               red
  329.     code                   navy blue
  330.     codealpha              dark green
  331.     assembly               gold
  332.     escape character       red
  333.  
  334. but note that at this stage, we've only used the following:
  335.  
  336.     function colour        [ and ] as function brackets, plus function names
  337.     comment colour         comments
  338.     directive colour       initial directive keywords, plus "*",
  339.                            "->", "with", "has" and "class" when used
  340.                            in a directive context
  341.     quoted text colour     singly- or doubly-quoted text
  342.     foreground colour      code in directives
  343.     code colour            code in statements
  344.     property colour        property, attribute and class names when
  345.                            used within "with", "has" and "class"
  346.  
  347. For instance,
  348.  
  349.      Object -> bottle "~Heinz~ bottle"
  350.  
  351. would give us the array
  352.  
  353.      DDDDDDDDDDFFFFFFFQQQQQQQQQQQQQQQQ
  354.  
  355. (F being foreground colour; it doesn't really matter what colour
  356. values the spaces have).
  357.  
  358. (d)  Colour refinement
  359.  
  360. The next operation is "colour refinement", which includes a number
  361. of things.
  362.  
  363. Firstly, any characters with colour Q (quoted-text) which have special
  364. meanings are given "escape-character colour" instead.  This applies
  365. to "~", "^", "\" and "@" followed by (possibly) another "@" and a
  366. number of digits.
  367.  
  368. Next we look for identifiers.  An identifier for these purposes includes
  369. a number, for it is just a sequence of:
  370.  
  371.      "_" or "$" or "#" or "0" to "9" or "a" to "z" or "A" to "Z".
  372.  
  373. The initial colouring of an identifier tells us its context.  We're
  374. only interested in those in foreground colour (these must be used
  375. in the body of a directive) or code colour (used in statements).
  376.  
  377. If an identifier is in code colour, then:
  378.  
  379.     If it follows an "@", recolour the "@" and the identifier in
  380.        assembly-language colour.
  381.     Otherwise, unless it is one of the following:
  382.  
  383.       "box"  "break"  "child"  "children"  "continue"  "default"
  384.       "do"  "elder"  "eldest"  "else"  "false"  "font"  "for"  "give"
  385.       "has"  "hasnt"  "if"  "in"  "indirect"  "inversion"  "jump"
  386.       "metaclass"  "move"  "new_line"  "nothing"  "notin"  "objectloop"
  387.       "ofclass"  "or"  "parent"  "print"  "print_ret"  "provides"  "quit"
  388.       "random"  "read"  "remove"  "restore"  "return"  "rfalse"  "rtrue"
  389.       "save"  "sibling"  "spaces"  "string"  "style"  "switch"  "to"
  390.       "true"  "until"  "while"  "younger"  "youngest"
  391.  
  392.     we recolour the identifier to "codealpha colour".
  393.  
  394. On the other hand, if an identifier is in foreground colour, then we
  395. check it to see if it's one of the following interesting keywords:
  396.  
  397.       "first"  "last"  "meta"  "only"  "private"  "replace"  "reverse"
  398.       "string"  "table"
  399.  
  400. If it is, we recolour it in directive colour.
  401.  
  402. Thus, after colour refinement we arrive at the final colour scheme:
  403.  
  404.     function colour        [ and ] as function brackets, plus function names
  405.     comment colour         comments
  406.     quoted text colour     singly- or doubly-quoted text
  407.     directive colour       initial directive keywords, plus "*",
  408.                               "->", "with", "has" and "class" when used
  409.                               in a directive context, plus any of the
  410.                               reserved directive keywords listed above
  411.     property colour        property, attribute and class names when
  412.                               used within "with", "has" and "class"
  413.     foreground colour      everything else in directives
  414.     code colour            operators, numerals, brackets and statement
  415.                               keywords such as "if" or "else" occurring
  416.                               inside routines
  417.     codealpha colour       variable and constant names occurring inside
  418.                               routines
  419.     assembly colour        @ plus assembly language opcodes
  420.     escape char colour     special or escape characters in quoted text
  421.  
  422. (e)  An example
  423.  
  424. Consider the following example stretch of code (which is not meant to
  425. be functional or interesting, just colourful):
  426.  
  427.    ! Here's the bottle:
  428.  
  429.    Object -> bottle "bottle marked ~DRINK ME~"
  430.      with name "bottle" "jar" "flask",
  431.           initial "There is an empty bottle here.",
  432.           before
  433.           [; LetGo:                      ! For dealing with water
  434.                 if (noun in bottle)
  435.                     "You're holding that already (in the bottle).";
  436.           ],
  437.      has  container;
  438.  
  439.    [ ReadableSpell i j k;
  440.      if (scope_stage==1)
  441.      {   if (action_to_be==##Examine) rfalse;
  442.          rtrue;
  443.      }
  444.      @set_cursor 1 1;
  445.    ];
  446.  
  447.    Extend "examine" first
  448.                    * scope=ReadableSpell            -> Examine;
  449.  
  450. Here are the initial colourings:
  451.  
  452.    ! Here's the bottle:
  453.    CCCCCCCCCCCCCCCCCCCC
  454.  
  455.    Object -> bottle "bottle marked ~DRINK ME~"
  456.    DDDDDDDDDDFFFFFFFQQQQQQQQQQQQQQQQQQQQQQQQQQ
  457.      with name "bottle" "jar" "flask",
  458.    FFDDDDDPPPPPQQQQQQQQFQQQQQFQQQQQQQD
  459.           initial "There is an empty bottle here.",
  460.    FFFFFFFPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQD
  461.           before
  462.    FFFFFFFPPPPPP
  463.           [; LetGo:                      ! For dealing with water
  464.    FFFFFFFfSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCCCCCCCCCCCCCCCCCCCCCCCC
  465.                 if (noun in bottle)
  466.    SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
  467.                     "You're holding that already (in the bottle).";
  468.    SSSSSSSSSSSSSSSSSQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQS
  469.           ],
  470.    SSSSSSSfD
  471.      has  container;
  472.    FFDDDDDPPPPPPPPPD
  473.  
  474.    [ ReadableSpell i j k;
  475.    fffffffffffffffSSSSSSS
  476.      if (scope_stage==1)
  477.    SSSSSSSSSSSSSSSSSSSSS
  478.      {   if (action_to_be==##Examine) rfalse;
  479.    SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
  480.          rtrue;
  481.    SSSSSSSSSSSS
  482.      }
  483.    SSS
  484.      @set_cursor 1 1;
  485.    SSSSSSSSSSSSSSSSSS
  486.    ];
  487.    fD
  488.  
  489.    Extend "examine" first
  490.    DDDDDDDQQQQQQQQQFFFFFF
  491.                    * scope=ReadableSpell            -> Examine;
  492.    FFFFFFFFFFFFFFFFDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDDDFFFFFFFD
  493.  
  494. (Here F=foreground, D=directive, f=function, S=code (S for
  495. "statement"), C=comment, P=property, Q=quoted text.)  And here is
  496. the refinement:
  497.  
  498.    ! Here's the bottle:
  499.    CCCCCCCCCCCCCCCCCCCC
  500.  
  501.    Object -> bottle "bottle marked ~DRINK ME~"
  502.    DDDDDDDDDDFFFFFFFQQQQQQQQQQQQQQQEQQQQQQQQEQ
  503.      with name "bottle" "jar" "flask",
  504.    FFDDDDDPPPPPQQQQQQQQFQQQQQFQQQQQQQD
  505.           initial "There is an empty bottle here.",
  506.    FFFFFFFPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQD
  507.           before
  508.    FFFFFFFPPPPPP
  509.           [; LetGo:                      ! For dealing with water
  510.    FFFFFFFfSSIIIIISSSSSSSSSSSSSSSSSSSSSSSCCCCCCCCCCCCCCCCCCCCCCCC
  511.                 if (noun in bottle)
  512.    SSSSSSSSSSSSSSSSSIIIISSSSIIIIIIS
  513.                     "You're holding that already (in the bottle).";
  514.    SSSSSSSSSSSSSSSSSQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQS
  515.           ],
  516.    SSSSSSSfD
  517.      has  container;
  518.    FFDDDDDPPPPPPPPPD
  519.  
  520.    [ ReadableSpell i j k;
  521.    fffffffffffffffSSSSSSS
  522.      if (scope_stage==1)
  523.    SSSSSSIIIIIIIIIIISSIS
  524.      {   if (action_to_be==##Examine) rfalse;
  525.    SSSSSSSSSSIIIIIIIIIIIISSIIIIIIIIISSSSSSSSS
  526.          rtrue;
  527.    SSSSSSSSSSSS
  528.      }
  529.    SSS
  530.      @set_cursor 1 1;
  531.    SSAAAAAAAAAAASISIS
  532.    ];
  533.    fD
  534.  
  535.    Extend "examine" first
  536.    DDDDDDDQQQQQQQQQFDDDDD
  537.                    * scope=ReadableSpell            -> Examine;
  538.    FFFFFFFFFFFFFFFFDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDDDFFFFFFFD
  539.  
  540. (where E = escape characters, A = assembly and I = "codealpha", that
  541. is, identifiers cited in statement code).
  542.  
  543. --
  544. Graham Nelson | graham@gnelson.demon.co.uk | Oxford, United Kingdom
  545.